package com.progenies.ecg.asm31.model.codeparts.lines;

import java.util.Arrays;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import com.progenies.ecg.asm31.util.BytecodeInstructionsUtils;
import com.progenies.ecg.asm31.util.GenerationUtils;
import com.progenies.ecg.asm31.util.VariableRegister.Variable;
import com.progenies.ecg.model.ValueReference;

public class CreateArrayLinePart<T> extends ConfigurableLineCodePart
{
	
	private Class<T> classType;
	private int arrayDimensions[];
	private String variableName;
	private ValueReference<?> varReference[];
	private ValueReference<?> matrixReference[][];
	private T value;

	public CreateArrayLinePart(Class<T> classType, String variableName, int arrayDimensions[])
	{
		this.classType=classType;
		this.arrayDimensions= Arrays.copyOf(arrayDimensions, arrayDimensions.length);
		this.variableName=variableName;
	}
	
	public CreateArrayLinePart(Class<T> classType, String variableName, ValueReference<?> varReference[])
	{
		this.classType=classType;
		this.varReference= Arrays.copyOf(varReference, varReference.length);
		this.variableName=variableName;
	}

	public CreateArrayLinePart(Class<T> classType, String variableName, ValueReference<?> matrixReference[][])
	{
		this.classType=classType;
		this.matrixReference= Arrays.copyOf(matrixReference, matrixReference.length);
		this.variableName=variableName;
	}

	public CreateArrayLinePart(Class<T> classType, T value, String variableName)
	{
		this.classType=classType;
		this.value=value;
		this.variableName=variableName;
	}
	
	
	
	@Override
	public void generateBytecode() {
		
		if(this.visitor==null )
			throw new IllegalStateException("LinePart has not been configurated, please use ClassGenerator");

		//creates new array and store in stack
		if(value!=null)
			BytecodeInstructionsUtils.createNewArrayIntoStackAndPopulate(visitor, Type.getType(classType), value);
		else if(varReference!=null)
			BytecodeInstructionsUtils.createNewArrayIntoStackAndPopulate(visitor, Type.getType(classType), varReference, getParent(), varRegister);
		else if(matrixReference!=null)
			BytecodeInstructionsUtils.createNewArrayIntoStackAndPopulate(visitor, Type.getType(classType), matrixReference, getParent(), varRegister);
		else if(arrayDimensions==null || arrayDimensions.length==0)
			throw new RuntimeException("Array dimensions not specified");
		else
			BytecodeInstructionsUtils.createNewArrayIntoStack(visitor, Type.getType(classType), arrayDimensions);
		
		//store the value into the variable (if i must to)
		if(this.variableName!=null)
		{
			Variable var=varRegister.getVariable(variableName);
			if(var.isLocal())
				visitor.visitVarInsn(var.getType().getOpcode(Opcodes.ISTORE), var.getIndex());
			else if(var.isStatic())
				visitor.visitFieldInsn(Opcodes.PUTSTATIC, GenerationUtils.getFieldOwner(var.getName(), parent), var.getName(), var.getType().getDescriptor());
			else
			{
				//i must insert "this" before the value. I'll insert at the end and move it later to the start
				BytecodeInstructionsUtils.insertLocalVariableIntoStack(visitor, varRegister, "this");
				visitor.visitInsn(Type.getType(classType).getSize()==1?Opcodes.DUP_X1:Opcodes.DUP2_X1); //duplicate the last value of size 2 ('this') and insert it at the start
				visitor.visitInsn(Opcodes.POP); //delete the extra reference
				visitor.visitFieldInsn(Opcodes.PUTFIELD, GenerationUtils.getFieldOwner(var.getName(), parent), var.getName(), var.getType().getDescriptor());
			}
				
		}
		else //empty stack
		{
			visitor.visitInsn(Opcodes.POP);
		}
		
	}



	public Class<T> getClassType() {
		return classType;
	}

	public void setClassType(Class<T> classType) {
		this.classType = classType;
	}
	
	public int[] getArrayDimensions() {
		return  Arrays.copyOf(arrayDimensions, arrayDimensions.length);
	}

	public void setArrayDimensions(int[] arrayDimensions) {
		this.arrayDimensions = Arrays.copyOf(arrayDimensions, arrayDimensions.length);
	}

	public String getVariableName() {
		return variableName;
	}

	public void setVariableName(String variableName) {
		this.variableName = variableName;
	}

}
